Projet Python

Professeur : Safwan CHENDEB

Date de rendu : 28 Janvier 2024

Trinôme : Nicolas RENOIR (CCC) 33% - Killian FOURNIER (CCC) 33% - Lucas LOPES (CCC) 33%



Sommaire :

1- Mini rapport

2- Analyse des données

3- Interprétations & visualisations + scraping

4- Comparaison avec l'année 2020

5- Django







1- Mini rapport¶

Introduction¶

Contexte Général¶

Le marché immobilier est très changeant selon le contexte économique et politique. Ainsi, l'accès à des données fiables et à jour est devenu essentiel, c'est pourquoi le gouvernement français à mis à disposition le jeu de données 'Demandes de valeurs foncières' qui permettent de connaître les transactions immobilières intervenues au cours des cinq dernières années sur le territoire métropolitain et les DOM-TOM.

Ainsi, notre projet a pour but d'explorer et d'analyser le jeu de données 'Demandes de valeurs foncières' pour comprendre des tendances, analyser des phénomènes et en tirer des conclusions pertinentes, le tout en utilisant le langage Python.

Le jeu de données¶

Le jeu de données Demandes de valeurs foncières contient de multiples précisions sur les transactions immobilières (valeur de la transaction, adresse du bien, caractéristiques du bien...). Ces données sont plus ou moins complètes, ce qui va nous amener à les nettoyer pour qu'elles soient pleinement utilisables.

Technologies utilisées¶

Nous allons utiliser le langage Python pour réaliser ce projet car il est très efficace lorsqu’il faut traiter un grand nombre de données. Pour lire les données nous allons utiliser Pandas et pour la visualisation nous allons utiliser matplotlib, seaborn ou encore folium. Enfin, nous ferons une vitrine d'analyse et de visualisation avec Django permettant à l’utilisateur d’interagir avec les données.

Ratio de contribution¶

Nous nous sommes réparti le travail en 3 parts égales.

2- Analyse des données¶

Dans cette partie nous allons charger les données que nous avons obtenu sur le site du gouvernement puis les nottoyer pour qu'elles soient exploitables. Par la suite, nous allons les explorer rapidements pour découvrir les caractéristiques des datasets (nom des colonnes, nombre de lignes, etc).

2.1 - Chargement des données¶

In [1]:
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('full.csv', sep=(','))
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1411711681.py:4: DtypeWarning: Columns (10,12,14,16,17,18,20,22,35,36) have mixed types. Specify dtype option on import or set low_memory=False.
  df = pd.read_csv('full.csv', sep=(','))

Nous remarquons plusieurs colonnes intéressantes que nous allons pouvoir exploiter (Code département, valeur foncière, surface,...). On remarque également que la latitude et la longitude nous permettrons sans doute d'afficher une carte.

In [2]:
df.shape
Out[2]:
(4617590, 40)

Nous remarquons qu'il y a plus de 4 millions de lignes, représentant une grande quantité d'informations. Ces lignes ne sont sans doute pas toutes utiles.

2.2 - Nettoyage¶

Le nettoyage de données est important lorsque l'on souhaite analyser des données. Il sert à traiter les données brutes pour les rendre utilisables et représentatives (Détection des valeurs abérrantes, Suppression des valeurs manquantes..).

On a remarqué que certaines colonnes étaient constituées uniquement de NaN on décide donc de les supprimer. On effectue cela car elles ne sont pas exploitables dans notre projet :

In [3]:
df = df.dropna(axis=1, how='all')

Nous avons également observé la présence de lignes identiques, que nous avons supprimé :

In [4]:
df = df.drop_duplicates()

Grâce à cette opération, nous avons supprimé le 345 000 lignes, ce qui va nous servir à avoir des données proches de la réalité.

In [5]:
df.shape
Out[5]:
(4273660, 40)

Nous avons également remarqué que les codes postaux/ numéro d'adresse / nombre de pièces principales étaient enregistrés en float, ce qui n'était pas logique, ce sont des nombres entiers. Nous les avons donc converti en int.

In [6]:
df['code_postal'] = df['code_postal'].fillna(0)
df['code_postal'] = df['code_postal'].astype(int)

df['adresse_numero'] = df['adresse_numero'].fillna(0)
df['adresse_numero'] = df['adresse_numero'].astype(int)

df['code_postal']
Out[6]:
0           1000
1           1480
2           1480
3           1480
4           1480
           ...  
4617585    75016
4617586    75016
4617587    75016
4617588    75006
4617589        0
Name: code_postal, Length: 4273660, dtype: int64

Nous avons remarqué que les appartements d'un immeuble ont chacun comme valeur le prix de l'immeuble (ce qui fausse tous les calculs). Nous allons donc trier sur les id parcelle pour en avoir que des différents :

In [7]:
print('Nombre de parcelle en double :', len(df['id_parcelle']) - len(df['id_parcelle'].unique()))
Nombre de parcelle en double : 1875019
In [8]:
df.drop_duplicates(subset='id_parcelle', keep=False, inplace=True)

3 - Interprétation & Visualisation¶

Maintenant que nous avons nettoyé notre DataFrame, nous allons visualiser les données pour les analyser. Nous allons d'abord voir les différents types de mutations ayant eu lieux en 2022.

In [9]:
import seaborn as sns 

plt.title('Type de mutation',fontsize='12')
sns.countplot(y='nature_mutation', data=df)
Out[9]:
<Axes: title={'center': 'Type de mutation'}, xlabel='count', ylabel='nature_mutation'>

On remarque une dominance des ventes sur l'année 2022 ce qui semble normal. D'autre part nous allons nous intéresser à la valeur de ces ventes. Nous allons donc afficher le prix au m² (entre la surface réelle bati & la valeur foncière).

In [10]:
plt.scatter(df['surface_reelle_bati'], df['valeur_fonciere'], alpha=0.5, color='green')

plt.xlabel('surface réelle bati (m²)')
plt.ylabel('Valeur foncière (€)')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.show()

Nous remarquons plusieurs choses :

  • Il y a des valeurs aberrantes dans les surfaces

  • Il y a des valeurs aberrantes dans les valeurs foncières

Sinon les valeurs sont situées à la base de notre graphique.

Pour comprendre d'où viennent ces valeurs aberrantes, regardons la moyenne des valeurs foncières par départements.

In [11]:
df['valeur_fonciere'] = df['valeur_fonciere'].astype(float)
moyenne_par_departement = df.groupby('code_departement')['valeur_fonciere'].mean()

print(moyenne_par_departement)
code_departement
1      252915.298254
2      112024.011686
3      112626.093707
4      203568.554434
5      149792.605358
           ...      
974    281413.748196
29     167222.506366
2A     409119.627986
2B     218762.600368
30     225527.662074
Name: valeur_fonciere, Length: 99, dtype: float64

Maintenant que nous avons les valeurs foncières moyenne par département, nous allons regarder le département avec la valeur foncière moyenne la plus élevée (probablement celui qui contiendra les valeurs aberrantes) :

In [12]:
print('La valeur foncière moyenne la plus élevée est de', moyenne_par_departement.max(), 'dans le', moyenne_par_departement.idxmax())
La valeur foncière moyenne la plus élevée est de 7425867.287267152 dans le 56

Nous remarquons que cette valeur est anormalement évelée par rapport aux autres, ce qui peut-être lié à des valeurs aberrantes. Pour s'en assurer, on va tracer la relation entre la surface réelle bâtie et la valeur foncière dans le 56 :

In [13]:
df_56 = df[df['code_departement'] == 56]

plt.scatter(df_56['surface_reelle_bati'], df_56['valeur_fonciere'], alpha=0.5, color='green')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')

plt.show()

Effectivement, il il a des valeurs aberrantes qui sont beaucoup plus élevées que la plupart des autres, ce qui fausse notre valeur foncière moyenne dans ce département. Pour trouver la cause de ces valeurs abérrantes allons afficher la valeur foncière moyenne par rapport à la surface réelle bati en fonction du type local :

In [14]:
df_56_appart = df[(df['type_local'] == 'Appartement') & (df['code_departement'] == 56)]
df_56_maison = df[(df['type_local'] == 'Maison') & (df['code_departement'] == 56)]
df_56_dependance = df[(df['type_local'] == 'Dépendance') & (df['code_departement'] == 56)]
df_56_local_industriel = df[(df['type_local'] == 'Local industriel. commercial ou assimilé') & (df['code_departement'] == 56)]
In [15]:
plt.scatter(df_56_appart['surface_reelle_bati'], df_56_appart['valeur_fonciere'], alpha=0.5, color='blue', label='Appartement')
plt.scatter(df_56_maison['surface_reelle_bati'], df_56_maison['valeur_fonciere'], alpha=0.5, color='orange', label='Maison')
plt.scatter(df_56_dependance['surface_reelle_bati'], df_56_dependance['valeur_fonciere'], alpha=0.5, color='red', label='Dépendance')
plt.scatter(df_56_local_industriel['surface_reelle_bati'], df_56_local_industriel['valeur_fonciere'], alpha=0.5, color='green', label='Local industriel/commercial')

plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.legend()

plt.show()

Nous voyons que les valeurs aberrantes sont liées aux types Dépendance et Local industriel/commercial. Affichons maintenant ce graphique sans les types Dépendance et Local industriel/commercial :

In [16]:
plt.scatter(df_56_appart['surface_reelle_bati'], df_56_appart['valeur_fonciere'], alpha=0.5, color='blue', label='Appartement')
plt.scatter(df_56_maison['surface_reelle_bati'], df_56_maison['valeur_fonciere'], alpha=0.5, color='orange', label='Maison')

plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.legend()

plt.show()

Sans les types Dépendance et Local industriel/commercial il n'y a presque pas de valeur aberrantes, nous pouvons donc en conclure que ces valeurs étaient liés à de gros locaux industriels.

Nous allons maintenant utiliser une autre surface pour calculer le prix au m² surface Carrez : basé sur la surface de plancher avec au moins 1,80 m de plafond. Cette surface est couramment utilisée pour calculer le prix au m². Enfin , nous allons pouvoir comparer ces 2 rapports.

Nous voyons qu'il y a plusieurs surfaces Carrez :

  • Surface Carrez du 1er lot,
  • Surface Carrez du 2eme lot,
  • Surface Carrez du 3eme lot,
  • Surface Carrez du 4eme lot,
  • Surface Carrez du 5eme lot

Nous allons les explorer pour voir laquelle est la plus utile.

In [17]:
s_carrez = ['lot1_surface_carrez','lot2_surface_carrez','lot3_surface_carrez','lot4_surface_carrez','lot5_surface_carrez']
for i in s_carrez:
    nb_nan = df[i].isna().sum()
    nb_lignes = len(df)
    print(f'Nombre de NaN dans la colonne '{i}': {(nb_nan*100)/nb_lignes} %')
Nombre de NaN dans la colonne 'lot1_surface_carrez': 98.46355267116614 %
Nombre de NaN dans la colonne 'lot2_surface_carrez': 99.85138860372967 %
Nombre de NaN dans la colonne 'lot3_surface_carrez': 99.96360072957788 %
Nombre de NaN dans la colonne 'lot4_surface_carrez': 99.98743087693236 %
Nombre de NaN dans la colonne 'lot5_surface_carrez': 99.99317513679586 %

On remarque que les colonnes sont quasiment toutes composées de NaN sauf la colonne lot1_surface_carrez. Nous allons donc utiliser cette colonne pour calculer le prix du mettre carré.

In [18]:
df['prix_m_2'] = df.valeur_fonciere / df.lot1_surface_carrez
print(df[df['prix_m_2'].notna()]['prix_m_2'])
216          557.142857
374         1313.105558
569         4232.199028
589         3910.226747
743         2630.850180
               ...     
4617464    10206.185567
4617479    10470.956720
4617491    10000.000000
4617500     9476.489967
4617540    15260.323160
Name: prix_m_2, Length: 27009, dtype: float64

Nous allons maintenant trier ces prix/m² par département.

In [19]:
prix_m_2_dep = df.groupby('code_departement')['prix_m_2'].mean()
print(prix_m_2_dep)
code_departement
1      2654.297720
2      1620.022921
3      3312.924064
4      1831.878801
5      2633.495242
          ...     
974    2716.516912
29     2192.442693
2A     4897.430483
2B     3454.565274
30     2221.763134
Name: prix_m_2, Length: 99, dtype: float64

On peut aussi afficher le rapport valeur foncière sur loi Carrez

In [20]:
plt.scatter(df['lot1_surface_carrez'], df['valeur_fonciere'], alpha=0.5, color='green')

plt.xlabel('Surface loi Carrez (m²)')
plt.ylabel('Valeur foncière (€)')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.show()

Nous remarquons qu'il y a beaucoup moins de valeur aberrante que dans nos précédentes visualisations. Ainsi ce rapport est beaucoup plus pertinent pour évaluer le prix au m².

Je peux également afficher la répartition du prix au mètre carré dans toute la France :

In [21]:
import plotly.express as px

prix_m_2_dep_sorted_all = prix_m_2_dep.sort_values(ascending=False)

fig = px.pie(names=prix_m_2_dep_sorted_all.index, values=prix_m_2_dep_sorted_all.values, title='Prix au mètre carré en France')

fig.update_traces(textposition='inside', textinfo='percent+label', insidetextorientation='radial', textfont_size=10)

fig.show()

Avec ces données, nous avons comme idée d'aller chercher une base de données contenant le nombre d'habitant par département en 2022. Pour cela nous allons scraper ces données à partir d'un site web :

In [22]:
import requests
from bs4 import BeautifulSoup
import csv

url = 'https://ville-data.com/nombre-d-habitants/nombre-d-habitants-par-departement'
response = requests.get(url)

scrap = BeautifulSoup(response.text, 'html.parser')

with open('population_data.csv', 'w', newline='') as csvfile:
    csv = csv.writer(csvfile, delimiter='\t')

    csv.writerow(['code', 'nom', 'hab'])

    for row in scrap.select('table tr')[1:]:
        columns = row.find_all('td')
        code = columns[0].text.strip()
        nom = columns[1].text.strip()
        pop = columns[2].text.strip()
        csv.writerow([code, nom, pop])
In [23]:
df_popu = pd.read_csv('population_data.csv', sep=('\t'), encoding='UTF-8')
df_popu = df_popu.sort_values('code', ascending= True)
In [24]:
print('Le', df_popu.loc[df_popu['hab'].idxmax(), 'code'], 'est le plus peuplé avec', df_popu.hab.max(), 'habitants')
Le 59 est le plus peuplé avec 2592185 habitants
In [25]:
print('Le', df_popu.loc[df_popu['hab'].idxmin(), 'code'], 'est le moins peuplé avec', df_popu.hab.min(), 'habitants')
Le 48 est le moins peuplé avec 75700 habitants

Avec ces données, nous souhaitons afficher la surface de terrain par habitant :

On fait d'abord la somme des surfaces de terrain du 59 et du 48 :

In [26]:
sum_surface_59 = df[df['code_departement'] == 59]['surface_terrain'].sum()
print(sum_surface_59)

sum_surface_48 = df[df['code_departement'] == 48]['surface_terrain'].sum()
print(sum_surface_48)
62631870.0
28885244.0

On le divise par le nombre d'habitants :

In [27]:
sum_surface_59_hab = sum_surface_59 / df_popu.hab.max()
print(sum_surface_59_hab)

sum_surface_48_hab = sum_surface_48 / df_popu.hab.min()
print(sum_surface_48_hab)
24.161805581005986
381.5752179656539
In [28]:
plt.bar(['59', '48'], [sum_surface_59_hab, sum_surface_48_hab], color=['blue', 'green'])
plt.xlabel('Département')
plt.ylabel('Surface de terrain par habitants')
plt.show()

Nous voyons que le rapport surface de terrain par habitants est beaucoup plus élevé dans le 48, car il y a beaucoup moins d'habitants.

Nous pouvons afficher le nombre d'habitants dans un graphique :

In [29]:
import plotly.express as px

fig = px.pie(df_popu, values='hab', names='nom', title='Répartition de la population par département')
fig.update_traces(textposition='inside', textinfo='percent+label', insidetextorientation='radial', textfont_size=10)
fig.show()

Nous pouvons voir la densité de population sur la carte de France et à partir des données scrappées :

In [62]:
import plotly.express as px
import geopandas as gpd

gj = gpd.read_file('departements-version-simplifiee.geojson')
df_map = pd.merge(gj, df_popu, on='code')

fig = px.choropleth_mapbox(df_popu, geojson=gj, color='hab',
                            locations='code',
                            center = {'lat': 46.23, 'lon': 2.2},
                            featureidkey='properties.code',
                            mapbox_style='carto-positron', zoom = 4)

fig.show()

Nous pouvons afficher la valeur foncière moyenne possédée par personne dans le 59 par rapport au 48 :

In [31]:
prix_59 = prix_m_2_dep.loc[59]
print(prix_59)
3982.224580474231
In [32]:
prix_59_pers = df_popu.hab.max() / prix_59
prix_59_pers
Out[32]:
650.9389281333059
In [33]:
prix_48 = prix_m_2_dep.loc[48]
print(prix_48)
1103.1757626739773
In [34]:
prix_48_pers = df_popu.hab.min() / prix_48
prix_48_pers
Out[34]:
68.62007176128624
In [35]:
plt.bar(['59', '48'], [prix_59_pers, prix_48_pers], color=['blue', 'green'])
plt.xlabel('Département')
plt.ylabel('Valeur foncière moyenne')
plt.show()

Nous remarquons que dans le 59 et pour un habitant, les valeurs foncières sont en moyenne 7 fois plus élevée que dans le 48.

Nous pouvons afficher les 3 départements les plus peuplés en France :

In [36]:
df_popu_sorted = df_popu.sort_values(by='hab', ascending=False)

top3_departements = df_popu_sorted.head(3)
In [37]:
fig = px.bar(top3_departements, x='code', y='hab', labels={'code': 'Code département', 'hab': 'Nombre d\'habitants'},
             title='Les 3 départements les plus peuplés en France')

fig.show()

Nous pouvons les comparer avec les 3 départements les plus chèrs au mètre carré :

In [38]:
prix_m_2_dep_sorted = prix_m_2_dep.sort_values(ascending=False).head(3)
print(prix_m_2_dep_sorted)
code_departement
75    13014.102997
72    12348.279410
61     9068.274111
Name: prix_m_2, dtype: float64
In [39]:
fig = px.bar(prix_m_2_dep_sorted, x=prix_m_2_dep_sorted.index, y='prix_m_2',
             labels={'prix_m_2': 'Prix par mètre carré'},
             title='Les 3 départements les plus chèrs au mètre carré')

fig.update_layout(xaxis_title='Département', yaxis_title='Prix au mètre carré')

fig.update_xaxes(type='category')
fig.show()

On observe que le 75 figure dans les deux classements, ce qui signifi qu'il est à la fois parmis les plus peuplés en France mais également les plus chers. À noter que la préfectures (Paris) est la plus grande ville de France. Nous voyons donc que le département le plus chère de France est aussi un des plus peuplé (ces deux phénomènes sont liés).

Nous pouvons observer le type de local le plus fréquent lors des ventes en 2022 :

In [40]:
sns.countplot(y='type_local', data=df)
Out[40]:
<Axes: xlabel='count', ylabel='type_local'>

Nous pouvons également comparer 2 départements en terme de type de local : Prenons le 78 (Yvelines) et le 35 (Ille-et-Vilaine).

In [41]:
df_75 = df[df['code_departement'] == 74]
df_35 = df[df['code_departement'] == 35]
df_75['Département'] = 75
df_35['Département'] = 35

combined_df = pd.concat([df_75, df_35])
sns.countplot(y='type_local', hue='Département', data=combined_df)
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1283902776.py:3: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1283902776.py:4: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

On voit que le nombre de ventes de maison dans le 35 est largement plus important que dans le 75 tous simplement car il y a beaucoup plus de maison dans le 35.

Nous pouvons observer l'évolution du prix minimum d'une transaction :

In [42]:
df_ev = df.copy()
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').min().plot() 
Out[42]:
<Axes: xlabel='date_mutation'>

Évolution du prix maximum d'une transaction :

In [43]:
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').max().plot() 
Out[43]:
<Axes: xlabel='date_mutation'>

Évolution du prix moyen d'une transaction :

In [44]:
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').mean().plot() 
Out[44]:
<Axes: xlabel='date_mutation'>

On voit un pic au mois de décembre ce qui est souvent le cas en fin d'années (ficalement intéressant).

Nous allons voir une cartographie des expropriations en 2022 :

In [45]:
df_expro =df[df['latitude'].notna() & (df['longitude'].notna()) & (df['nature_mutation'] == 'Expropriation')]

import folium
carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place  in df_expro[['latitude', 'longitude', 'nom_commune']].itertuples():
    carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Out[45]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Comme nous avons vu plus tôt, il y a des valeurs aberrantes dans le 56, nous allons le vérifier sur la carte de France :

In [46]:
df_popu['code'] = df_popu['code'].astype(str).str.zfill(2)
dep_manquant3 = ['57', '67', '68','971', '972', '973', '974', '976']
df_popu = df_popu[~df_popu['code'].isin(dep_manquant3)]
df_popu = df_popu.sort_values(by='code')
dep_manquant = ['57', '67', '68']
gj = gj[~gj['code'].isin(dep_manquant)]
dep_manquant2 = [971, 972, 973, 974]
df = df[~df['code_departement'].isin(dep_manquant2)]
df['code_departement'] = df['code_departement'].astype(str).str.zfill(2)
gj['code'] = gj['code'].astype(str).str.zfill(2)
gj = gj.sort_values(by='code')
df_map = pd.merge(gj, df_popu, on='code')
In [47]:
dep_manquant4 = ['57', '67', '68',971, 972, 973, 974, 976]
moyenne_par_departement = moyenne_par_departement[~moyenne_par_departement.index.isin(dep_manquant4)]

moyenne_par_departement.index = moyenne_par_departement.index.astype(str).str.zfill(2)
moyenne_par_departement_update = moyenne_par_departement[~moyenne_par_departement.index.duplicated(keep='first')]
In [48]:
df_moyenne = pd.DataFrame(moyenne_par_departement_update)
df_moyenne['code'] = df_moyenne.index
df_moyenne
df_map = pd.merge(df_map, df_moyenne[['code', 'valeur_fonciere']], 
                  left_on='code', 
                  right_on='code', 
                  how='inner')
df_map
fig = px.choropleth_mapbox(df_map, geojson=gj, color='valeur_fonciere',
                            locations='code',
                            center = {'lat': 46.23, 'lon': 2.2},
                            featureidkey='properties.code',
                            mapbox_style='carto-positron', zoom = 4)

fig.show()

Les valeurs aberrantes dans le 56 se retrouvent bien sur notre carte de France. Comme vu précédemment, c'est lié aux locaux industriels.

4- Comparaison avec l'année 2020¶

In [49]:
df2 = pd.read_csv('full_2020.csv', sep=(','))
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/862735889.py:1: DtypeWarning:

Columns (10,12,14,16,17,18,20,22,35,36) have mixed types. Specify dtype option on import or set low_memory=False.

In [50]:
df2.shape
Out[50]:
(3518587, 40)

Nettoyage¶

In [51]:
df2 = df2.dropna(axis=1, how='all')
df2 = df2.drop_duplicates()
df2.drop_duplicates(subset='id_parcelle', keep=False, inplace=True)

Reprenons maintenant notre exemple de comparaison entre le 75 et le 35 en terme de type de bien vendus :

In [52]:
df_75 = df[df['code_departement'] == '75']
df_35 = df[df['code_departement'] == '35']
df_75['Département'] = '75'
df_35['Département'] = '35'

combined_df = pd.concat([df_75, df_35])
sns.countplot(y='type_local', hue='Département', data=combined_df)
plt.title('Comparaison de type de bien vendu entre le 75 et le 35 en 2022')
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1960003802.py:3: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1960003802.py:4: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

In [53]:
df2_75 = df2[df2['code_departement'] == 75]
df2_35 = df2[df2['code_departement'] == 35]
df2_75['Département'] = 75
df2_35['Département'] = 35

combined_df2 = pd.concat([df2_75, df2_35])
sns.countplot(y='type_local', hue='Département', data=combined_df2)
plt.title('Comparaison de type de bien vendu entre le 75 et le 35 en 2020')
plt.show()
/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1698341241.py:3: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

/var/folders/kh/yx0zyp_n763396m112z_7_6c0000gn/T/ipykernel_26457/1698341241.py:4: SettingWithCopyWarning:


A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy

Nous voyons qu'il y a beaucoup plus de vente d'appartement en 2020 dans le 75 par rapport à 2022. Ceci est lié au COVID et aux confinements qui ont rendu la vie difficile dans une appartement.

Nous allons maintenant regarder la différence entre 2022 et 2020 pour le rapport valeur foncière/surface réelle bati dans le 75 :

In [54]:
df_75 = df[df['code_departement'] == '75']

plt.scatter(df_75['surface_reelle_bati'], df_75['valeur_fonciere'], alpha=0.5, color='green')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.title('Prix mètre carré dans le 75 en 2022')

plt.show()
In [55]:
df_75 = df2[df2['code_departement'] == 75]

plt.scatter(df_75['surface_reelle_bati'], df_75['valeur_fonciere'], alpha=0.5, color='green')

plt.xlim(-10000, 270000)
plt.ylim(-40000000, 800000000)

plt.xlabel('Surface réelle bâtie (m²)')
plt.ylabel('Valeur foncière (€)')
plt.title('Prix mètre carré dans le 75 en 2020')

plt.show()

Nous voyons que les valeurs sont beaucoup plus éparpillées en 2020 (valeurs aberrantes).

Voyons maintenant le prix moyen d'une transaction en 2022 :

In [56]:
df_ev = df
df_ev['date_mutation'] = pd.to_datetime(df_ev['date_mutation'])
df_ev = df_ev.set_index('date_mutation').sort_index()
df_Visu = df_ev[['valeur_fonciere']]
df_Visu.resample('M').mean().plot()
Out[56]:
<Axes: xlabel='date_mutation'>

En comparaison avec le prix moyen d'une transaction en 2020 :

In [57]:
df_ev2 = df2
df_ev2['date_mutation'] = pd.to_datetime(df_ev2['date_mutation'])
df_ev2 = df_ev2.set_index('date_mutation').sort_index()
df_Visu2 = df_ev2[['valeur_fonciere']]
df_Visu2.resample('M').mean().plot() 
Out[57]:
<Axes: xlabel='date_mutation'>

Il est clair que l'année 2020 a été beaucoup plus changeante que l'années 2022. Nous voyons même que l'entrée dans le confinement en mars 2020 a provoqué une montée des prix.

Nous pouvons également comparer les expropriations de 2022 :

In [58]:
carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place  in df_expro[['latitude', 'longitude', 'nom_commune']].itertuples():
    carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Out[58]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Avec les expropriations de 2020 :

In [59]:
df_expro2 =df2[df2['latitude'].notna() & (df2['longitude'].notna()) & (df2['nature_mutation'] == 'Expropriation')]

carte = folium.Map(location=[43.327408, -1.032999], zoom_start=5)
for ind, lat, lon, place  in df_expro2[['latitude', 'longitude', 'nom_commune']].itertuples():
    carte.add_child(folium.RegularPolygonMarker(location=[lat,lon], popup=place,fill_color='red', radius=10))
carte
Out[59]:
Make this Notebook Trusted to load map: File -> Trust Notebook

Nous voyons qu'il y a beaucoup plus d'expropriations en 2020 qu'en 2022, principalement dans les grandes villes.

Enfin, nous pouvons observer la différence de type de local dans les demandes de valeurs foncières entre 2022 et 2020 :

In [60]:
sns.countplot(y='type_local', data=df)
Out[60]:
<Axes: xlabel='count', ylabel='type_local'>
In [61]:
sns.countplot(y='type_local', data=df2)
Out[61]:
<Axes: xlabel='count', ylabel='type_local'>

On remarque qu'il y a beaucoup plus de vente de maison en 2020 qu'en 2022 (33% de plus)

5- Django¶

Notre vidéo sur Django est disponible dans le dossier du projet.